/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.lock.service.redis;

import io.iec.edp.caf.commons.exception.ExceptionLevel;
import io.iec.edp.caf.commons.runtime.CafEnvironment;
import io.iec.edp.caf.commons.transaction.JpaTransaction;
import io.iec.edp.caf.commons.transaction.TransactionPropagation;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.commons.utils.StringUtils;
import io.iec.edp.caf.core.context.BizContextManager;
import io.iec.edp.caf.core.context.ICAFContextService;
import io.iec.edp.caf.core.session.ICafSessionService;
import io.iec.edp.caf.lock.service.api.api.DistributedLock;
import io.iec.edp.caf.lock.service.api.api.DistributedLockFactory;
import io.iec.edp.caf.lock.service.api.exception.DLErrorDefinition;
import io.iec.edp.caf.lock.service.api.repositories.BatchLockRepo;
import io.iec.edp.caf.lock.service.api.repositories.LockRepo;
import io.iec.edp.caf.lock.service.api.utils.DataValidator;
import io.iec.edp.caf.lockservice.api.*;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.lang.Nullable;

import javax.persistence.EntityManager;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 数据锁服务实现类
 *
 * @author wangyandong
 * @date 2019/8/22 16:04
 *
 */
@Deprecated
@Slf4j
//todo 被com.inspur.edp.cdp.coderule.runtime.server.lock.DistributedLockService依赖
public class DistributedLockService implements ILockService {

    private DistributedLockFactory factory;
    private ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
    // @Autowired
    private LockRepo repo;
    // @Autowired
    private BatchLockRepo batchRepo;
    private ICAFContextService contextService;
    private BizContextManager bizContextManager;
    private ICafSessionService sessionService;
    EntityManager entityManager = SpringBeanUtils.getBean(EntityManager.class);
    private static int batchSize=0;

    public DistributedLockService(DistributedLockFactory factory, ICAFContextService contextService,
                                  BizContextManager bizContextManager, ICafSessionService sessionService, LockRepo repo,
                                  BatchLockRepo batchRepo) {
        this.factory = factory;
        this.contextService = contextService;
        this.bizContextManager = bizContextManager;
        this.sessionService = sessionService;
        this.repo = repo;
        this.batchRepo = batchRepo;
    }

    @Override
    public LockResult addLock(String mkId, String dataCat, String dataId, DataLockOptions option, String funcId, String comment, @Nullable DistributedLockOptions distributedLockOptions) {
        DataValidator.checkForEmptyString(mkId, "mkId");
        DataValidator.checkForEmptyString(dataCat, "dataCat");
        DataValidator.checkForEmptyString(dataId, "dataId");
        DataValidator.checkForNullReference(option, "options");
        LockResult result = this.internalAddLock(mkId, dataCat, dataId, option, funcId, comment, null, distributedLockOptions);

        return result;
    }

    /**
     * @param mkId    模块ID
     * @param dataCat 数据种类
     * @param dataId  数据ID
     * @param option  数据锁的设置选项
     * @param funcId  加锁的功能Id
     * @param comment 备注
     * @return
     */
    @Override
    public LockResult addLock(String mkId, String dataCat, String dataId, DataLockOptions option, String funcId, String comment) {
        return this.addLock(mkId, dataCat, dataId, option, funcId, comment, null);
    }

    @Override
    @Deprecated
    public BatchLockResult addBatchLock(String mkId, String dataCat, List<String> dataIds, String groupId, DataLockOptions option, String funcId, String comment, @Nullable DistributedLockOptions distributedLockOptions) {
        return this.addBatchLock(mkId, dataCat, dataIds, groupId, option.getPersistenceTime(), funcId, comment);
    }

    @Override
    @Deprecated
    public BatchLockResult addBatchLock(String mkId, String dataCat, List<String> dataIds, String groupId, DataLockOptions option, String funcId, String comment) {
        return this.addBatchLock(mkId, dataCat, dataIds, groupId, option, funcId, comment, null);
    }

    /**
     * 批量锁加锁方法
     *
     * @param mkId            模块ID
     * @param dataCat         数据种类
     * @param dataIds         数据ID列表
     * @param groupId         批量锁标识，可空
     * @param persistenceTime 持续时间
     * @param funcId          功能ID
     * @param comment         备注
     * @return
     */
    @Override
    public BatchLockResult addBatchLock(String mkId, String dataCat, List<String> dataIds, @Nullable String groupId, Duration persistenceTime, String funcId, String comment) {
        long time=System.currentTimeMillis();
        DataValidator.checkForEmptyString(mkId, "mkId");
        DataValidator.checkForEmptyString(dataCat, "dataCat");
        DataValidator.checkForNullReference(dataIds, "dataIDs");
        DataValidator.checkForNullReference(persistenceTime, "persistenceTime");
        if (isSessionNotValid(this.contextService.getSessionId())) {
            throw new io.iec.edp.caf.lockservice.api.DistributedLockException("pfcomm", "Gsp_Svc_DistributedLock_1002", "Session:" + this.contextService
                    .getSessionId() + " is not online!", null, ExceptionLevel.Error, false);
        }

        if (StringUtils.isEmpty(groupId)) {
            groupId = UUID.randomUUID().toString();
        }
        BatchLockResult result = new BatchLockResult();
        result.setGroupId(groupId);
        if(dataIds==null||dataIds.size()==0){
            result.setSuccess(true);
            return result;
        }
        DistributedLock distributedLock=null;
        //根据上下文获取租户ID
        int tenantId = this.contextService.getTenantId();
        String resourcePrefx= String.format("%s-%s-%s", tenantId, mkId, dataCat);
        Duration expiryTime = Duration.ofSeconds(30) ;
        try{
            long time1=System.currentTimeMillis();
            distributedLock=this.factory.createMultiLock(resourcePrefx,dataIds,expiryTime);
            if(log.isDebugEnabled()){
                long cost1=System.currentTimeMillis()-time1;
                log.debug("createMultiLock: "+cost1+"ms"+" currentThread: "+Thread.currentThread().getId()+" resource: "+mkId+"-"+dataCat+"-"+dataIds.size());
            }
            if (distributedLock == null){
                log.error("DistributedMultiLock get nothing!");
                result.setSuccess(false);
                return result;
                //调接口加锁是否成功是通过返回的结果进行判断，如果抛异常表明加锁失败，多数调接口的人并未捕获该异常，以为是发生了错误，所以此处还是直接返回false,并记录个日志，表明加不上锁是分布式锁未加上的标志
//                throw new DistributedLockException("pfcomm", DLErrorDefinition.AddLock_IsAcquired_Error,
//                        "DistributedLock get nothing!", null, ExceptionLevel.Error, false);
            }
            //清理过期锁
            this.batchRepo.deleteByExpiredTime(this.contextService.getCurrentDateTime());

            //查询已有的锁数据
            long time2=System.currentTimeMillis();
            List<BatchLockEntity> entities = queryBatchLockEntityByDataIds(mkId, dataCat, dataIds);
            if(log.isDebugEnabled()){
                long cost2=System.currentTimeMillis()-time2;
                log.debug("queryBatchLock: "+cost2+"ms"+" currentThread: "+Thread.currentThread().getId()+" resource: "+mkId+"-"+dataCat);
            }
            if (entities != null && entities.size() > 0) {
                long time3=System.currentTimeMillis();
                int count = deleteBatchLockBySession(entities);
                if(log.isDebugEnabled()){
                    long cost3=System.currentTimeMillis()-time3;
                    log.debug("deleteBatchLock: "+cost3+"ms"+" currentThread: "+Thread.currentThread().getId()+" resource: "+mkId+"-"+dataCat);
                }
                if (entities.size() != count) {
                    result.setLockedEntities(entities);
                    result.setSuccess(false);
                    if(log.isInfoEnabled()){
                        log.info("Cannot add lock.");
                    }
                    return result;
                }
            }
            JpaTransaction tran = JpaTransaction.getTransaction();
            try {
                tran.begin(TransactionPropagation.REQUIRES_NEW);
                EntityManager em = SpringBeanUtils.getBean(EntityManager.class);
                if(batchSize==0){
                    batchSize = CafEnvironment.getEnvironment().getProperty("spring.jpa.properties.jdbc.batch_size",Integer.class,10);
                }

                List<BatchLockEntity> entityList = createBatchEntities(mkId, dataCat, dataIds, groupId, persistenceTime, funcId, comment);
                long time4=System.currentTimeMillis();
                for (int i = 0; i < entityList.size(); i++) {
                    em.persist(entityList.get(i));
                    if ((i+1) % batchSize == 0 || i == entityList.size() - 1) {
                        em.flush();
                        em.clear();
                    }
                }
                if(log.isDebugEnabled()){
                    long cost4=System.currentTimeMillis()-time4;
                    log.debug("saveBatchLock: "+cost4+"ms"+" currentThread: "+Thread.currentThread().getId()+" resource: "+mkId+"-"+dataCat);
                }
                result.setSuccess(true);
                long time5=System.currentTimeMillis();
                tran.commit();
                if(log.isDebugEnabled()){
                    long costCom=System.currentTimeMillis()-time5;
                    long costTotal=System.currentTimeMillis()-time;
                    log.debug("batchLockCommit: "+costCom+"ms"+" currentThread: "+Thread.currentThread().getId()+" resource: "+mkId+"-"+dataCat);
                    log.debug("batchLockTime: "+costTotal+"ms"+" currentThread: "+Thread.currentThread().getId()+" resource: "+mkId+"-"+dataCat);
                }
                if(log.isInfoEnabled()){
                    log.info("Add lock success.");
                }
                return result;
            } catch (Throwable e) {
                tran.rollback();
                throw e;
            }
        }catch (Throwable e){
            throw e;
        }finally {
            try {
                if(distributedLock!=null){
                    distributedLock.multiUnlock();
                }
            } catch (Exception e) {
                log.error("multiunlock: ",e);
//                throw new RuntimeException(e);
            }
        }
    }

    private List<BatchLockEntity> queryBatchLockEntityByDataIds(String mkId, String dataCat, List<String> dataIds) {
        List<BatchLockEntity> entities = new ArrayList<>();
        if (dataIds != null) {
            int size = dataIds.size();//获取dataIds的长度
            int num = size / 999 + 1;//每次查询不超过999条
            if (num == 1 || dataIds == null) {
                return this.batchRepo.findAllByMkIdAndCategoryIdAndDataIdIn(mkId, dataCat, dataIds);
            } else {
                for (int i = 0; i < num; i++) {
                    int start = i * 999;
                    int end = (i + 1) * 999;
                    if (end >= size) {
                        end = size;
                    }
                    List<BatchLockEntity> entitiesSub = this.batchRepo.findAllByMkIdAndCategoryIdAndDataIdIn(mkId, dataCat, dataIds.subList(start, end));
                    entities.addAll(entitiesSub);
                }
            }
        }
        return entities;
    }

    private int deleteBatchLockBySession(List<BatchLockEntity> entities) {
        Map<String, List<BatchLockEntity>> map = (Map)entities.stream().collect(Collectors.groupingBy(BatchLockEntity::getSessionId));
        int count = 0;
        for (Map.Entry<String, List<BatchLockEntity>> m : map.entrySet()) {
            //sessionId不为空 && sessionId已过期，则移除
            if (!StringUtils.isEmpty(m.getKey()) && isSessionNotValid(m.getKey())) {
                List<BatchLockEntity> entityList = (List)m.getValue();

                List<String> ids = new ArrayList<String>();
                for (BatchLockEntity sp : entityList) {
                    ids.add(sp.getId());
                }
//                this.batchRepo.deleteAllByIdIn(ids);
//                count += ids.size();
                //使用in时各数据库对in条件个数有限制，例如oracle是1000，分批次进行删除,每次删不超过999条
//                this.batchRepo.deleteAllByIdIn(ids);
                int size=ids.size();
                int num=size/999+1;
                if(num==1){
                    this.batchRepo.deleteAllByIdIn(ids);
                }else{
                    for(int i=0;i<num;i++){
                        int start = i * 999;
                        int end = (i + 1) * 999;
                        if (end >= size) {
                            end = size;
                        }
                        this.batchRepo.deleteAllByIdIn(ids.subList(start, end));
                    }
                }
                count += ids.size();
            }
        }

        return count;
    }


    @Override
    @Deprecated
    public boolean removeLock(String lockId, boolean removeSharedLock, String sessionId) {
        DataValidator.checkForEmptyString(lockId, "lockId");
        return this.removeLock(lockId);
    }

    /**
     * 根据锁编号解锁
     *
     * @param lockId 锁编号
     * @return 是否成功的标识
     */
    public boolean removeLock(String lockId) {
        DataValidator.checkForEmptyString(lockId, "lockId");
        JpaTransaction tran = JpaTransaction.getTransaction();
        try {
            tran.begin(TransactionPropagation.REQUIRES_NEW);
            if (this.repo.findById(lockId).isPresent()) {
                this.repo.deleteById(lockId);
            }
            tran.commit();
            return true;
        } catch (Throwable e) {
            tran.rollback();
            throw e;
        }
    }

    @Override
    public boolean removeSessionLock(String sessionId, int tenantId) {

        DataValidator.checkForEmptyString(sessionId, "sessionId");
        JpaTransaction tran = JpaTransaction.getTransaction();
        try {
            tran.begin(TransactionPropagation.REQUIRES_NEW);
            this.repo.deleteBySessionId(sessionId);
            tran.commit();
            return true;
        } catch (Throwable ex) //没有提供sessionid调用报错a
        {
            tran.rollback();
            //捕获的异常类型改为Throwable后，再抛DistributedLockException不合适，强转Throwable为Exception时，如果Throwable为Error强转会出错
            throw ex;
//            throw new io.iec.edp.caf.lockservice.api.DistributedLockException("pfcomm", DLErrorDefinition.RemLock_Sessionid_Error, null, (Exception)ex, ExceptionLevel.Error, false);
        }
    }

    @Override
    public boolean removeBatchLock(String groupId) {
        JpaTransaction tran = JpaTransaction.getTransaction();
        try {
            tran.begin(TransactionPropagation.REQUIRES_NEW);
            //若批次号没对应锁则也算删锁成功
            this.batchRepo.deleteByGroupId(groupId);
            tran.commit();
            return true;
        } catch (Throwable ex) //没有提供groupid调用报错
        {
            tran.rollback();
            throw ex;
//            throw new io.iec.edp.caf.lockservice.api.DistributedLockException("pfcomm", DLErrorDefinition.RemLock_BathLock_Error, null,(Exception) ex, ExceptionLevel.Error, false);
        }
    }

    @Override
    public boolean removeBatchLockByContext(String contextId) {
        JpaTransaction tran = JpaTransaction.getTransaction();
        try {
            tran.begin(TransactionPropagation.REQUIRES_NEW);
            //若批次号没对应锁则也算删锁成功
            this.batchRepo.deleteByContextId(contextId);
            tran.commit();
            return true;
        } catch (Throwable ex) //没有提供groupid调用报错
        {
            tran.rollback();
            throw ex;
//            throw new io.iec.edp.caf.lockservice.api.DistributedLockException("pfcomm", DLErrorDefinition.RemLock_BathLock_Error, null, (Exception)ex, ExceptionLevel.Error, false);
        }
    }

    @Override
    public boolean removeInvalidLocks(int tenantId) {
        // to do
        return true;
    }

    /// <summary>
    /// 删除批量锁(排他锁)
    /// </summary>
    /// <param name="lockIDs">批量锁ID</param>
    /// <returns>是否批量删锁成功</returns>
    private boolean removeBatchLock(List<String> lockIDs) {
        JpaTransaction tran = JpaTransaction.getTransaction();
        try {
            tran.begin(TransactionPropagation.REQUIRES_NEW);
            // this.batchRepo.deleteAllByIdIn(lockIDs);
            deleteBatchLockEntityByLockIDs(lockIDs);
            tran.commit();
            return true;                         //都成功或没有锁也算删锁失败
        } catch (Throwable ex) //没有提供lockids调用报错
        {
            tran.rollback();
            throw ex;
//            throw new io.iec.edp.caf.lockservice.api.DistributedLockException("pfcomm", DLErrorDefinition.RemLock_BathLock_Error, null,(Exception) ex, ExceptionLevel.Error, false);
        }
    }

    private void deleteBatchLockEntityByLockIDs(List<String> lockIDs) {
        if (lockIDs != null) {
            int size = lockIDs.size();//获取dataIds的长度
            int num = size / 2 + 1;//每次删除不超过500条
            if (num == 1 || lockIDs == null) {
                this.batchRepo.deleteAllByIdIn(lockIDs);
            } else {
                for (int i = 0; i < num; i++) {
                    int start = i * 2;
                    int end = (i + 1) * 2;
                    if (end >= size) {
                        end = size;
                    }
                    this.batchRepo.deleteAllByIdIn(lockIDs.subList(start, end));
                }
            }
        }
    }


    /// <summary>
    /// 内部执行加锁逻辑
    /// </summary>
    private LockResult internalAddLock(String mkId, String dataCat, String dataId,
                                       DataLockOptions option, String funcId, String comment, String groupId, @Nullable DistributedLockOptions distributedLockOptions) {

        long time = System.currentTimeMillis();

        LockResult result = new LockResult();
        //结果默认为不成功
        result.setSuccess(false);
        //根据上下文获取租户ID
        int tenantId = this.contextService.getTenantId();
        DistributedLock distributedLock = null;
        //根据资源、租户等信息构造锁编号
        String lockId = UUID.randomUUID().toString();
        // 获取持久资源
        String resource = String.format("%s-%s-%s-%s", tenantId, mkId, dataCat, dataId);

        //如果不为实例级锁，首先检查session是否在线，不在线抛异常
        if (this.isSessionNotValid(this.contextService.getSessionId())) {
            throw new io.iec.edp.caf.lockservice.api.DistributedLockException("pfcomm", DLErrorDefinition.AddLock_IsSession_Error,
                    "Session:" + this.contextService.getSessionId() + " is not online!", null, ExceptionLevel.Error, false);
        }

        LockEntity entity = this.createLockEntity(lockId, mkId, dataCat, dataId, option, funcId, comment);

        Duration expiryTime = distributedLockOptions == null ? Duration.ofSeconds(20) : distributedLockOptions.getExpiryTime();
        try {
            long time1 = System.currentTimeMillis();
            distributedLock = this.factory.createLock(resource, expiryTime);
            if(log.isDebugEnabled()){
                long cost1=System.currentTimeMillis()-time1;
                log.debug("createLock: "+cost1+"ms"+" currentThread: "+Thread.currentThread().getId()+" resource: "+mkId+"-"+dataCat+"-"+dataId);
            }
            //若得不到分布式锁则直接返回
            if (distributedLock == null || !distributedLock.isAcquired())
                throw new DistributedLockException("pfcomm", DLErrorDefinition.AddLock_IsAcquired_Error,
                        "DistributedLock get nothing!", null, ExceptionLevel.Error, false);
            JpaTransaction tran = JpaTransaction.getTransaction();
            try {
                tran.begin(TransactionPropagation.REQUIRES_NEW);
                //从数据库获取锁信息
                long time2 = System.currentTimeMillis();
                List<LockEntity> entities = this.repo.findByMkIdAndCategoryIdAndDataId(mkId, dataCat, dataId);
                if(log.isDebugEnabled()){
                    long cost2=System.currentTimeMillis()-time2;
                    log.debug("selectTime: "+cost2+"ms"+" currentThread: "+Thread.currentThread().getId()+" resource: "+mkId+"-"+dataCat+"-"+dataId);
                }

                // 无锁直接加锁
                if (entities == null || entities.size() == 0) {
                    result.setLockedEntities(null);
                    long time3 = System.currentTimeMillis();
                    this.repo.save(entity);
                    if(log.isDebugEnabled()){
                        long cost3=System.currentTimeMillis()-time3;
                        log.debug("noLockAdd: "+cost3+"ms"+" currentThread: "+Thread.currentThread().getId()+" resource: "+mkId+"-"+dataCat+"-"+dataId);
                    }
                    result.setSuccess(true);
                    result.setLockId(entity.getId());
                } else {
                    //获取排它锁
                    LockStatus lockStatus = this.checkValidate(entities.get(0));

                    // 共享锁的处理 - 不检查共享锁的存在性，排它锁存在但可以被替换掉，直接删除排它锁
                    if (option.getReplacedScope() == ReplacedScope.Share) {
                        switch (lockStatus) {
                            case Shared:
                            case Unlocked:
                                this.repo.save(entity);
                                result.setSuccess(true);
                                result.setLockId(entity.getId());
                                break;
                            case Invalid:
                            case Replaceable:
                                if(this.repo.findById(entities.get(0).getId()).isPresent()){
                                    this.repo.deleteById(entities.get(0).getId());
                                }
                                this.repo.save(entity);
                                result.setSuccess(true);
                                result.setLockId(entity.getId());
                                break;
                            case Irreplaceable:
                                result.setLockedEntities(entities);
                                result.setSuccess(false);
                                break;
                        }
                    } else {
                        //加日志排查自己锁自己问题
                        if(log.isInfoEnabled()){ //判断一把日志级别可减少序列化过程
                            log.info("exclusive"+mkId+"/"+dataCat+"/"+dataId);
                        }
                        //加排它锁：不存在有效的排它锁 && 不存在有效的共享锁
                        if (lockStatus == LockStatus.Irreplaceable) {
                            result.setLockedEntities(entities);
                            result.setSuccess(false);
                        } else {
                            List<LockStatus> sharedLockStatus = this.checkValidate(entities);
                            // 共享锁，在以下情况不可加锁：1、存在有效且不可替换锁、共享锁（认为不允许替换）；2、存在多个有效可替换锁；
                            boolean validSharedLockExisting = false;
                            if (entities != null && entities.size() > 0) {
                                for (int index = 0; index < entities.size(); index++) {
                                    // 锁有效且不可替换
                                    if (sharedLockStatus.get(index) == LockStatus.Irreplaceable ||
                                            sharedLockStatus.get(index) == LockStatus.Shared) {
                                        //加日志排查自己锁自己问题
                                        if(log.isInfoEnabled()){ //判断一把日志级别可减少序列化过程
                                            log.info("lockvalid"+mkId+"/"+dataCat+"/"+dataId);
                                        }
                                        result.setLockedEntities(entities);
                                        result.setSuccess(false);
                                        validSharedLockExisting = true;
                                        break;
                                    }
                                }
                            }

                            if (validSharedLockExisting == false) {
                                //不存在有效的共享锁
//                                this.repo.deleteInBatch(entities);
                                long time4 = System.currentTimeMillis();
                                for(LockEntity entity1:entities){
                                    if(this.repo.findById(entity1.getId()).isPresent()){
                                        this.repo.deleteById(entity1.getId());
                                    }
                                }
                                if(log.isDebugEnabled()){
                                    long cost4=System.currentTimeMillis()-time4;
                                    log.debug("noShareDelete: "+cost4+"ms"+" currentThread: "+Thread.currentThread().getId()+" resource: "+mkId+"-"+dataCat+"-"+dataId+" entitySize: "+entities.size());
                                }
                                //新加锁
                                this.repo.save(entity);
                                result.setSuccess(true);
                                result.setLockId(entity.getId());

                            }
                        }
                    }
                }
                long timeCom=System.currentTimeMillis();
                tran.commit();

                if(log.isDebugEnabled()){
                    long costCom=System.currentTimeMillis()-timeCom;
                    long costTotal=System.currentTimeMillis()-time;
                    log.debug("lockCommit: "+costCom+"ms"+" currentThread: "+Thread.currentThread().getId()+" resource: "+mkId+"-"+dataCat+"-"+dataId);
                    log.debug("lockTime: "+costTotal+"ms"+" currentThread: "+Thread.currentThread().getId()+" resource: "+mkId+"-"+dataCat+"-"+dataId);
                }

                return result;
            } catch (Throwable ex) {
                tran.rollback();
                //throw new DistributedLockException("pfcomm", DLErrorDefinition.AddLock_GetLock_Error, null, ex, ExceptionLevel.Error, false);
                // throw new RuntimeException(ex.getMessage());
                throw ex;

            }
        } catch (Throwable e) {
            //throw new DistributedLockException("pfcomm", DLErrorDefinition.AddLock_GetLock_Error, null, e, ExceptionLevel.Error, false);
            // throw new RuntimeException(e.getMessage());
            throw e;
        } finally {
            try {
                if (distributedLock != null){
                    distributedLock.close();
                }
            } catch (IOException e) {
                log.error("unlock: ",e);
//                throw new RuntimeException(e);
            }
        }
    }

    private LockEntity createLockEntity(String lockId, String mkId, String dataCat, String dataId, DataLockOptions option, String funcId, String comment) {
        String sessionId = this.contextService.getSessionId();
        //String sessionId = "s0001";
//        LockEntity entity = LockEntity.builder()
//                .id(lockId)
//                .mkId(mkId)
//                .categoryId(dataCat)
//                .dataId(dataId)
//                .keepTime(option.getPersistenceTime())
//                .lockedScope(option.getLockedScope())
//                .replacedScope(option.getReplacedScope())
//                .funcId(funcId)
//                .sessionId(sessionId)
//                .contextId(this.contextService.getContextId())
//                .comment(comment)
//                .userId(this.contextService.getUserId())
//                .lockTime(this.contextService.getCurrentDateTime())
//                .build();
        var entity = new LockEntity();
//        entity.setId(lockId);
        entity.setMkId(mkId);
        entity.setCategoryId(dataCat);
        entity.setDataId(dataId);
        entity.setKeepTime(option.getPersistenceTime());
        entity.setLockedScope(option.getLockedScope());
        entity.setReplacedScope(option.getReplacedScope());
        entity.setFuncId(funcId);
        entity.setSessionId(sessionId);
        entity.setContextId(this.contextService.getContextId());
        entity.setComment(comment);
        entity.setUserId(this.contextService.getUserId());
        entity.setLockTime(this.contextService.getCurrentDateTime());
        entity.setSuName(this.contextService.getCurrentSU());
        return entity;
    }

    private List<BatchLockEntity> createBatchEntities(String mkId, String dataCat, List<String> dataIds, String groupId, Duration persistenceTime, String funcId, String comment) {
        List<BatchLockEntity> entities = new ArrayList<>();
        OffsetDateTime lockTime = this.contextService.getCurrentDateTime();
        OffsetDateTime expiredTime = (persistenceTime.getSeconds() == 0) ?
                OffsetDateTime.of(2037, 1, 19, 3, 14, 7, 59, ZoneOffset.UTC) : lockTime.plus(persistenceTime.getSeconds(), ChronoUnit.SECONDS);

        String sessionId = this.contextService.getSessionId();
        String contextId = this.contextService.getContextId();
        for (var dataId : dataIds) {
//            var entity = BatchLockEntity.builder()
//                    .id(UUID.randomUUID().toString())
//                    .mkId(mkId)
//                    .categoryId(dataCat)
//                    .dataId(dataId)
//                    .groupId(groupId)
//                    .sessionId(sessionId)
//                    .contextId(contextId)
//                    .funcId(funcId)
//                    .comment(comment)
//                    .expiredTime(expiredTime)
//                    .lockTime(lockTime)
//                    .build();
            var entity = new BatchLockEntity();
            entity.setId(UUID.randomUUID().toString());
            entity.setMkId(mkId);
            entity.setCategoryId(dataCat);
            entity.setDataId(dataId);
            entity.setGroupId(groupId);
            entity.setSessionId(sessionId);
            entity.setContextId(contextId);
            entity.setFuncId(funcId);
            entity.setComment(comment);
            entity.setExpiredTime(expiredTime);
            entity.setLockTime(lockTime);
            entity.setUserId(this.contextService.getUserId());
            entity.setSuName(this.contextService.getCurrentSU());
            // entity.setLocknavigationId(locknavigationid);
            entities.add(entity);
        }
        return entities;
    }

    /**
     * 检查上下文是否有效
     *
     * @param contextId
     * @return 上下文的有效状态
     */
    private boolean isContextNotValid(String contextId) {
        return bizContextManager.isExpired(contextId);
    }

    /**
     * 检查会话是否有效
     *
     * @param sessionId
     * @return 会话的有效状态
     */
    private boolean isSessionNotValid(String sessionId) {
        //return true;
        return sessionService.isExpired(sessionId);
    }

    /// <summary>
    /// 检查批量锁的有效状态
    /// </summary>
    /// <returns>锁的有效状态</returns>
    private List<LockStatus> checkValidate(List<LockEntity> locks) {
        List<LockStatus> result = new ArrayList<>();
        if (locks != null && locks.size() > 0) {
            for (int i = 0; i < locks.size(); i++) {
                result.add(checkValidate(locks.get(i)));
            }
        }
        return result;
    }

    //私有化方法
    /// <summary>
    /// 检查锁是否有效
    /// 锁状态：Unlocked为锁不存在；Invalid为锁已失效；Replaceable为锁有效可替换；Irreplaceable为锁有效不可替换；Shared为共享锁，可以加锁。
    /// </summary>
    private LockStatus checkValidate(LockEntity lockEntity) {
        LockStatus result = LockStatus.Invalid;
        //1 锁不存在
        if (lockEntity == null) {
            return LockStatus.Unlocked;
        }
        //2 根据时间判断是否无效
        long seconds = lockEntity.getKeepTime().getSeconds();
        OffsetDateTime lockedTime = lockEntity.getLockTime();
        OffsetDateTime expirationTime = lockedTime.plus(seconds, ChronoUnit.SECONDS);
        OffsetDateTime currentTime = this.contextService.getCurrentDateTime();
        if (seconds != 0 && (currentTime.compareTo(expirationTime) >= 0)) {
            return LockStatus.Invalid;                           // 表示锁存在但无效,后续可以加锁
        } else                                                              // 表示锁存在且有效，需要再检查
        {
            //3 根据范围(LockedScope)判断是否有效
            String locksessionId = lockEntity.getSessionId();
            String contextId = lockEntity.getContextId();
            String currentsessionId = this.contextService.getSessionId();
            String currentContextId = this.contextService.getContextId();
            switch (lockEntity.getLockedScope()) {
                case AppInstance:                             // 表示实例锁有效，需要近一步检查
                    result = LockStatus.Replaceable;
                    break;
                case Session:
                    boolean notValid= this.isSessionNotValid(locksessionId);
                    //加日志排查自己锁自己问题
                    if(log.isInfoEnabled()){ //判断一把日志级别可减少序列化过程
                        log.info("locksession:"+locksessionId+"/currentsession:"+currentsessionId+"/notvalid:"+notValid);
                    }
                    if (!locksessionId.equals(currentsessionId) && notValid) {
                        return LockStatus.Invalid;               // 表示锁存在但进程丢失,后续可以加锁
                    }
                    result = LockStatus.Replaceable;
                    break;                                                // 进程锁需要进一步检查
                case BizContext:                                          // 功能上下文
                    if (!contextId.equals(currentContextId) && this.isContextNotValid(contextId)) {
                        return LockStatus.Invalid;               // 表示锁存在但context无效,后续可以加锁
                    }
                    result = LockStatus.Replaceable;
                    break;
                default:
                    result = LockStatus.Invalid;
                    break;
            }
            //4 根据范围(ReplacedScope)判断是否可以替换
            String lockuserCode = lockEntity.getUserId();
            String currentuserCode = this.contextService.getUserId();
            switch (lockEntity.getReplacedScope()) {
                case Exclusive:                             // 独占锁，不允许后续加锁
                    result = LockStatus.Irreplaceable;
                    return result;
                case Session:                               // 进程锁，如果本进程内可替换
                    if (!locksessionId.equals(currentsessionId)) {
                        result = LockStatus.Irreplaceable;                // 其它进程的锁，不可加锁
                        return result;
                    }
                    break;
                case Self:
                    //加日志排查自己锁自己问题
                    if(log.isInfoEnabled()){ //判断一把日志级别可减少序列化过程
                        log.info("lockuser:"+lockuserCode+"/currentuser:"+currentuserCode);
                    }
                    // 同一用户可替换，可能多个进程用同一用户登录
                    if (!currentuserCode.equals(lockuserCode)) {
                        result = LockStatus.Irreplaceable;                // 其它用户锁，不可加锁
                        return result;
                    }
                    break;
                case Share:                                 // 共享锁，可加锁
                    return LockStatus.Shared;
            }
            return result;
        }
    }

    public LockStatus getLockStatus(LockEntity lockEntity) {
        return checkValidate(lockEntity);
    }













    //测试用
    private String getSessionId() {
        //return "s0001";
        return this.contextService.getSessionId();
    }


    private String insertBatchLock(String categoryid, String comments, String contextid, List<String> dataIds, OffsetDateTime expiredTime, String funcid, String groupId, OffsetDateTime lockTime, String mkid, String sessionid, String suname, String userid) {
        StringBuilder dataSql =
                new StringBuilder("insert into gspbatchlock " +
                        "(id, categoryid, comments, contextid, dataid, expiredtime, funcid, groupid, locktime, mkid, sessionid, suname, userid)" +
                        " values");
        dataIds.forEach(x -> {
            dataSql.append("(")
                    .append(UUID.randomUUID().toString())
                    .append(",")
                    .append(categoryid)
                    .append(",")
                    .append(comments)
                    .append(",")
                    .append(contextid)
                    .append(",")
                    .append(x)
                    .append(",")
                    .append(expiredTime)
                    .append(",")
                    .append(funcid)
                    .append(",")
                    .append(groupId)
                    .append(",")
                    .append(lockTime)
                    .append(",")
                    .append(mkid)
                    .append(",")
                    .append(sessionid)
                    .append(",")
                    .append(suname)
                    .append(",")
                    .append(userid)
                    .append(")")
                    .append(",");
        });
        return dataSql.substring(0, dataSql.length() - 1);
    }

    public static void main(String[] args) {
        String mkId = "mkId";
        String dataCat = "dataCat";
        String dataID = "dataID";
        String dataID1 = "dataID1";
        String dataID2 = "dataID2";
        ArrayList<String> strings = new ArrayList<>();
        strings.add(dataID);
        strings.add(dataID1);
        strings.add(dataID2);
        DataLockOptions option = new DataLockOptions();
        option.setLockedScope(LockedScope.Session);
        option.setReplacedScope(ReplacedScope.Session);
        option.setPersistenceTime(Duration.ofSeconds(120));
        String funcId = "funcId";
        String comment = "comment";
        DistributedLockOptions diop = new DistributedLockOptions();
        diop.setExpiryTime(Duration.ofSeconds(2));
        OffsetDateTime lockTime = OffsetDateTime.of(2037, 1, 19, 3, 14, 7, 59, ZoneOffset.UTC);
        OffsetDateTime expiredTime = OffsetDateTime.of(2037, 1, 19, 3, 14, 7, 59, ZoneOffset.UTC);
        // insertBatchLock(dataCat,comment,comment,strings,expiredTime,funcId,UUID.randomUUID().toString(),lockTime,comment,comment,comment,comment);

    }

}
